﻿using System;
using System.Diagnostics;
using System.Windows.Media;
using System.IO;
using System.Collections.Generic;
using System.Threading;

namespace WindowsPhoneAudioStreaming.Audio.Streaming {

    public class RawMP3MediaStreamSource :
        MediaStreamSource
    {

        public event EventHandler ErrorOnOpen;

        #region private constants

        ///// <summary>
        /////  ID3 version 1 tags are 128 bytes at the end of the file.
        /////  http://www.id3.org/ID3v1
        ///// </summary>
        //private const int ID3VERSION1TAGSIZE = 128;

        #endregion

        #region private variables

        /// <summary>
        /// The Mp3 stream being played back.
        /// </summary>
        private MP3PCMStream _mp3PCMStream;

        private WaveFormatExtensible _waveFormatExtensible = null;
        //private int _durationInMilliseconds = 0;
        //private byte[] _sampleBuffer;
        //private int _currentWriteBufferIndex = 0;
        //private int _currentReadBufferIndex = 0;
        //private int _bufferSize = 0;
        //private double _millisecondsPerFrame = 0;
        //private bool _sampleBubblingCanBegin = false;
        //private double _runningAccumulatedFrameSize = 0;
        //private long _runningAccumulatedFrameSizeCounter = 1;
        ////private AudioPreProcessorDelegate _audioPreProcessorDelegate = null;
        //private object _audioPreProcessorLock = new object();
        //private Dictionary<MediaSampleAttributeKeys, string> _emptyDict;
        //protected bool _initialized;
        //protected DateTime _startTime;
        /// <summary>
        /// 	Description of the MP3 data as a PCM data stream with same bitrate, channels, sampling frequency, etc
        /// </summary>
        private MediaStreamDescription audioStreamDescription;

        private readonly IPlayerOptions optionsGenerator;

        public IPlaybackOptions options;

        protected IPlaybackOptions LoadOptions()
        {
            TraceLogger.WriteTrace("Options Reloaded");
            return optionsGenerator.PlaybackOptions;
        }


        #endregion

        #region public constructors

        /// <summary>
        ///  Initializes a new instance of the RawMP3MediaStreamSource class.
        /// </summary>
        /// <param name="audioStream">
        /// Must be a Seekable stream containing MP3 data
        /// </param>
        /// <param name="generator"> </param>
        public RawMP3MediaStreamSource(Stream audioStream, IPlayerOptions generator)
            : this(generator)
        {
            _mp3PCMStream = new MP3PCMStream(audioStream);
            //if (audioStream is SeekableStream) {
            //    ((SeekableStream)audioStream).DownloadProgressChanged += RawMp3MediaStreamSource_DownloadProgressChanged;
            //}

            // Create a default WaveFormatExtensible as it is used in public properties
            _waveFormatExtensible = new WaveFormatExtensible();

        }

        public RawMP3MediaStreamSource(IPlayerOptions generator)
        {
            optionsGenerator = generator;
        }
        
        #endregion

        #region private methods

        /// <summary>
        /// Calculate the duration of audio based on the size of the buffer
        /// </summary>
        /// <param name="audioDataSize">the buffer size in bytes</param>
        /// <returns>The duration of that buffer</returns>
        private long AudioDurationFromBufferSize(uint audioDataSize)
        {
            if (_waveFormatExtensible.AverageBytesPerSecond == 0)
            {
                return 0;
            }

            return (long) audioDataSize*10000000/_waveFormatExtensible.AverageBytesPerSecond;
        }

        protected void InitializeFromStream(Stream audioStream)
        {
            this._mp3PCMStream = new MP3PCMStream(audioStream);
            //this._mp3PCMStream.SilentHappened += this.OnSilentHappened;
            this._waveFormatExtensible = new WaveFormatExtensible();
        }

        ///// <summary>
        ///// 	Adds the last PCM sample output to the Sample Buffer.
        ///// 	After the Sample Buffer is full, SampleReady will fire.
        ///// 	The Sample Buffer is sized to fill 5 seconds of audio as this
        ///// 	seems to be the default of MediaElement, where it plays after it has read
        ///// 	5 seconds of audio.
        ///// 	Currently, cannot determine when the Consumer is actually playing the audio.
        ///// </summary>
        //private void AddToSampleBuffer() {
        //    lock (_sampleBuffer) {
        //        if ((_currentWriteBufferIndex + _bufferSize) <= _sampleBuffer.Length) {
        //            _mp3PCMStream.Buffer.CopyTo(_sampleBuffer, _currentWriteBufferIndex);
        //        }
        //    }

        //    _currentWriteBufferIndex += _bufferSize;
        //    if (_currentWriteBufferIndex >= _sampleBuffer.Length) {
        //        _currentWriteBufferIndex = 0;
        //        _sampleBubblingCanBegin = true;
        //    }
        //}

        ///// <summary>
        ///// 	The raw PCM audio data is stored as 4 bytes per sample
        ///// 	The first 2 bytes are for the left channel and other 2 are for the right channel
        ///// 	The PCM sample is actually 16bit, so they are stored over 2 bytes in an array.
        ///// 	The data is converted 16 bit and stored in an int array for consumption through
        ///// 	the SampleReady event.
        ///// </summary>
        //private void CorrectAndBubbleCachedSampleData() {
        //    if (_sampleBubblingCanBegin) {
        //        Int16 sampleData = 0;
        //        int sampleWidth = _bufferSize;

        //        Int16[] correctedBuffer = new Int16[sampleWidth / 2];

        //        int correctedBufferIndex = 0;
        //        for (int sampleIndex = 0; sampleIndex < sampleWidth; sampleIndex += 2) {
        //            sampleData = BitConverter.ToInt16(_sampleBuffer, _currentReadBufferIndex + sampleIndex);
        //            correctedBuffer[correctedBufferIndex++] = sampleData;
        //        }

        //        if (SampleReady != null) {
        //            SampleReady(this, correctedBuffer);
        //        }

        //        _currentReadBufferIndex += sampleWidth;
        //        if (_currentReadBufferIndex >= (_sampleBuffer.Length - sampleWidth)) {
        //            _currentReadBufferIndex = 0;
        //        }
        //    }
        //}

        //private long CalculateSamplingLatencyInMilliseconds() {
        //    long latencyInMilliseconds = 0;

        //    if (_currentWriteBufferIndex > _currentReadBufferIndex) {
        //        latencyInMilliseconds = _currentWriteBufferIndex - _currentReadBufferIndex;
        //    } else {
        //        int sampleBufferLength = _sampleBuffer.Length;
        //        latencyInMilliseconds = (sampleBufferLength - _currentReadBufferIndex) + _currentWriteBufferIndex;
        //    }

        //    //TODO: This is a trial and error calculation. Change the latency to actual millisecond calculation
        //    return ((latencyInMilliseconds / _bufferSize) * 4);
        //}

        #endregion

        #region private event handlers

        //private void RawMp3MediaStreamSource_DownloadProgressChanged(object sender, double downloadProgress) {
        //    if (this.DownloadProgressChanged != null) {
        //        this.DownloadProgressChanged(this, downloadProgress);
        //    }
        //}

        public void InvokeErrorOnOpen()
        {
            EventHandler errorOnOpen = this.ErrorOnOpen;
            if (errorOnOpen != null)
            {
                errorOnOpen.Invoke(this, EventArgs.Empty);
            }
        }

        #endregion

        #region protected overridden methods

        /// <summary>
        ///  Perform a prelimary MP3 structure determination to calculate Duration and other details
        ///  necessary for the Consumer to play the PCM data at the correct rate/timing.
        /// </summary>
        protected override void OpenMediaAsync()
        {

            ThreadPool.QueueUserWorkItem(state =>
                                             {
                                                 try
                                                 {
                                                     var dictionary =
                                                         new Dictionary<MediaSourceAttributesKeys, string>();
                                                     var dictionary2 =
                                                         new Dictionary<MediaStreamAttributeKeys, string>();
                                                     var list = new List<MediaStreamDescription>();
                                                     this._mp3PCMStream.DecodeFrames(2);
                                                     // Decoding 2 frames appears to correctly determine the correct MP3 structure.
                                                     // Possible due to the first frame being a VBR frame in the case of VBR MP3 files

                                                     _waveFormatExtensible = new WaveFormatExtensible
                                                                                 {
                                                                                     FormatTag = 1,
                                                                                     Channels =
                                                                                         this._mp3PCMStream.ChannelCount,
                                                                                     SamplesPerSec =
                                                                                         this._mp3PCMStream.Frequency,
                                                                                     AverageBytesPerSecond =
                                                                                         this._mp3PCMStream.BitRate/8,
                                                                                     BitsPerSample = 16
                                                                                 };
                                                     this._waveFormatExtensible.BlockAlign =
                                                         (short)
                                                         (this._waveFormatExtensible.Channels*
                                                          (this._waveFormatExtensible.BitsPerSample/8));
                                                     this._waveFormatExtensible.Size = 0;
                                                     dictionary2[0] = this._waveFormatExtensible.ToHexString();
                                                     this.audioStreamDescription = new MediaStreamDescription(0,
                                                                                                               dictionary2);
                                                     list.Add(this.audioStreamDescription);
                                                     //this._durationInMilliseconds = Convert.ToInt32(0f);
                                                     //this._millisecondsPerFrame = (double)this._mp3PCMStream.LastHeader.GetMillisecondsPerFrame();
                                                     dictionary[MediaSourceAttributesKeys.Duration] = "0";
                                                     dictionary[MediaSourceAttributesKeys.CanSeek] = "false";

                                                     //this._bufferSize = this._mp3PCMStream.FrameSize;
                                                     //this._sampleBuffer = new byte[this._waveFormatExtensible.SamplesPerSec * 5 + this._bufferSize];

                                                     //var options = LoadOptions();
                                                     ChangeBufferLength(this._waveFormatExtensible.AverageBytesPerSecond*
                                                         options.BufferLengthInSeconds);

                                                     base.ReportOpenMediaCompleted(dictionary, list);
                                                     //this._initialized = true;
                                                     //this._startTime = DateTime.Now;
                                                 }
                                                 catch (Exception ex)
                                                 {
                                                     this.InvokeErrorOnOpen();
                                                     Debug.WriteLine(ex.Message);
                                                 }
                                             });
            // Initialize data structures to pass to the Media pipeline via the MediaStreamSource

        }

        protected virtual void ChangeBufferLength(int lengthInBytes)
        {
            
        }

        private long timeinc = 0;

        private long lastPosition;
        /// <summary>
        /// Parses the next sample from the requested stream and then calls ReportGetSampleCompleted
        /// to inform its parent MediaElement of the next sample.
        /// </summary>
        /// <param name="mediaStreamType">
        /// Should always be Audio for this MediaStreamSource.
        /// </param>
        protected override void GetSampleAsync(MediaStreamType mediaStreamType)
        {
            
            var emptyDict = new Dictionary<MediaSampleAttributeKeys, string>();
            MediaStreamSample audioSample = null;

            try
            {
                // If the request for 1 audio frame returns 1 frame of data, then proceed sending it up to the consumer
                if (_mp3PCMStream.DecodeFrames(1) == 1)
                {
                    //// Run the pre processor over the audio data before sending it off to the consumer
                    //lock (_audioPreProcessorLock)
                    //{
                    //    if (_audioPreProcessorDelegate != null)
                    //    {
                    //        _audioPreProcessorDelegate(_mp3PCMStream.Buffer);
                    //    }
                    //}
                    lastPosition = _mp3PCMStream.Position;
                    audioSample = new MediaStreamSample(
                        this.audioStreamDescription,
                        _mp3PCMStream,
                        _mp3PCMStream.Position,
                        _mp3PCMStream.FrameSize,
                        AudioDurationFromBufferSize((uint) _mp3PCMStream.FrameSize) + timeinc,
                        emptyDict);
                    timeinc += 100;

                    //AddToSampleBuffer();
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
            if (audioSample == null)
            {
                //// No data left due to end of stream or a problem decoding the data occurred.
                //// Signal to the consumer that there is no data left to provide.
                //audioSample = new MediaStreamSample(
                //    this._audioStreamDescription,
                //    null,
                //    0,
                //    0,
                //    0,
                //    emptyDict);

                audioSample= new MediaStreamSample(
                        this.audioStreamDescription,
                        _mp3PCMStream,
                        lastPosition,
                        _mp3PCMStream.FrameSize,
                        AudioDurationFromBufferSize((uint)_mp3PCMStream.FrameSize) + timeinc,
                        emptyDict);
                timeinc += 100;
            }

            //sampleCount++;
            //if (sampleCount%100 == 0)
            //{
            //    sampleCount = 0;
            //    UpdateMusicAndVideoHubTile();
            //}

            // Return the populated PCM sample data up to the consumer
            this.ReportGetSampleCompleted(audioSample);


            //CorrectAndBubbleCachedSampleData();
        }

        //private int sampleCount;




        /// <summary>
        /// TODO: Check all streams and other resources to release
        /// </summary>
        protected override void CloseMedia() {
            _mp3PCMStream.Close();
        }

        /// <summary>
        /// 	Unsupported
        /// </summary>
        /// <param name="diagnosticKind"></param>
        protected override void GetDiagnosticAsync(MediaStreamSourceDiagnosticKind diagnosticKind) {
            throw new NotSupportedException();
        }

        protected override void SeekAsync(long seekToTime)
        {
            ReportSeekCompleted(seekToTime);
        }

        /// <summary>
        /// 	Unsupported
        /// </summary>
        /// <param name="mediaStreamDescription"></param>
        protected override void SwitchMediaStreamAsync(MediaStreamDescription mediaStreamDescription) {
            throw new NotSupportedException();
        }

        #endregion



        #region public properties

        ///// <summary>
        /////  Gets or Sets the position of the Media in time
        ///// </summary>
        //public TimeSpan Position {
        //    get {
        //        double milliseconds = 0;
        //        int frameSize = 0;
        //        long position = _mp3PCMStream.Position;
        //        if (_mp3PCMStream.LastHeader != null) {
        //            if (position < _mp3PCMStream.Length) {
        //                _runningAccumulatedFrameSize += _mp3PCMStream.LastHeader.FrameSize;
        //                // Minel Pather: Use of an averaging FrameSize works well with VBR and should be the same for CBR (average of the constant is the constant)
        //                frameSize = Convert.ToInt32(_runningAccumulatedFrameSize / _runningAccumulatedFrameSizeCounter++);

        //                milliseconds = ((position / frameSize) * _millisecondsPerFrame);
        //                milliseconds -= CalculateSamplingLatencyInMilliseconds();
        //                if (milliseconds < 0) {
        //                    milliseconds = 0;
        //                }
        //            } else {
        //                frameSize = Convert.ToInt32(_runningAccumulatedFrameSize / _runningAccumulatedFrameSizeCounter);
        //                milliseconds = _mp3PCMStream.LastHeader.GetTotalMilliseconds((int)_mp3PCMStream.Length, frameSize, _millisecondsPerFrame);
        //            }
        //        }

        //        return TimeSpan.FromMilliseconds(milliseconds);
        //    }
        //    set {
        //        double milliseconds = value.TotalMilliseconds;
        //        double frameSize = (_runningAccumulatedFrameSize / _runningAccumulatedFrameSizeCounter);
        //        long position = (long)(frameSize * (milliseconds / _millisecondsPerFrame));
        //        _mp3PCMStream.Position = position;
        //    }
        //}

        //public int NumberOfChannels {
        //    get {
        //        return _waveFormatExtensible.Channels;
        //    }
        //}

        //public int AverageBytesPerSecond {
        //    get {
        //        //TODO: use the correct averaging for VBR files
        //        return _waveFormatExtensible.AverageBytesPerSecond;
        //    }
        //}

        //public int BitsPerSample {
        //    get {
        //        return _waveFormatExtensible.BitsPerSample;
        //    }
        //}

        //public bool BitrateIsConstant {
        //    get {
        //        bool bitrateIsConstant = false;
        //        if (_mp3PCMStream != null) {
        //            if (_mp3PCMStream.LastHeader != null) {
        //                bitrateIsConstant = (!(_mp3PCMStream.LastHeader.VBRHeader.MediaIsVBR));
        //            }
        //        }

        //        return bitrateIsConstant;
        //    }
        //}

        //public int SamplesPerSecond {
        //    get {
        //        return _waveFormatExtensible.SamplesPerSec;
        //    }
        //}

        //public int DurationInMilliseconds {
        //    get {
        //        return _durationInMilliseconds;
        //    }
        //}

        //public AudioTypeEnumeration AudioType {
        //    get {
        //        return AudioTypeEnumeration.MP3;
        //    }
        //}

        #endregion
    }
}
